use strict;
use warnings;

package RTx::AssetTracker::Graph;
use RT::URI;
use GraphViz;

my %AddedNodes = ();
my %AddedEdges = ();

sub Graph {

    my $class = shift;
    %AddedNodes = ();
    %AddedEdges = ();

    my %args = (
        Assets    => undef,  # An asset collection
        LinkDepth => 2,      # Number of links to follow
        SkipLinks => [],     # LinkType not to follow
        SkipTypes => [],     # Asset types not to display other than
                             # the ones in the Assets arg
        SkipStatus => ['retired'],    # Link statuses not to follow
        SkipAssets => 0,     # Skip assets named in next param
        SkipNames  => [ ],   # Names of assets not to plot ( next if $asset->Name =~ /$skip/i )
        Direction => 'Horizontal',
                     @_ );

    die "No asset collection" unless ref($args{Assets}) eq 'RTx::AssetTracker::Assets';

    for my $key qw( SkipLinks SkipTypes SkipStatus SkipNames ) {
        $args{$key} = [ $args{$key} ] unless ref $args{$key};
    }

    my $g = GraphViz->new(layout => 'dot',
                          rankdir => ($args{Direction} eq 'Horizontal' ? 0 : 1),
                          concentrate => 1,
                         );

    my %visited;
    while ( my $asset = $args{Assets}->Next ) {

        _addNode( Graph => $g, Asset => $asset, Style => 'filled', Shape => 'diamond',
                  Force => 1, # so attributes of starting nodes are set
                 );
        _visitNode( Graph => $g, Asset => $asset, SkipLinks => $args{SkipLinks},
                    Depth => $args{LinkDepth}, SkipTypes => $args{SkipTypes},
                    SkipStatus => $args{SkipStatus},
                    SkipNames => $args{SkipAssets} ? $args{SkipNames} : [ ],
                    Visited => \%visited );

    }


    return $g;

}

sub _visitNode {

    my %args = (
           Graph => undef,
           Asset => undef,
       SkipLinks => [],
       SkipTypes => [],
       SkipStatus => [],
       SkipNames => [],
           Depth => 0,
         Visited => {},
                    @_ );

    my $Asset = $args{Asset};

    die "No graph object" unless ref($args{Graph});
    die "No asset object" unless ref($Asset) eq 'RTx::AssetTracker::Asset';

    return $args{Graph} unless $args{Depth} > 0; # reached the limit

    return if $args{Visited}{ $Asset->id };
    $args{Visited}{ $Asset->id } = $Asset->Name;

    my $BaseLinks   = { Links => $Asset->_Links('Base'  ), End => 'Base'   };
    my $TargetLinks = { Links => $Asset->_Links('Target'), End => 'Target' };

    for my $Links ( $BaseLinks, $TargetLinks ) {
        LINKS:
        while ( my $Link = $Links->{Links}->Next ) {

            my $type;
            if ( $Links->{End} eq 'Base' ) { $type = $Link->Type }
            else { $type = invert($Link->Type) }

            next if grep { $type eq $_ } @{$args{SkipLinks}};

            my $other_asset
                = other_asset( URI => ($Links->{End} eq 'Base' ? $Link->Target
                                                               : $Link->Base) );
            next unless $other_asset && $other_asset->Name;

            for my $name ( @{$args{SkipNames}} ) {
                if ( $other_asset->Name =~ /$name/i ) {
                    next LINKS;
                    #$RT::Logger->error ( $other_asset->Name . " matches $name\n" );
                }
            }

            next if grep { $other_asset->Status eq $_ }  @{$args{SkipStatus}};

            next if grep { $other_asset->TypeObj->Name eq $_ } @{$args{SkipTypes}};

            _addEdge( Graph => $args{Graph}, Label => $Link->Type,
                      Base   => ( $Links->{End} eq 'Base' ? $Asset       : $other_asset ),
                      Target => ( $Links->{End} eq 'Base' ? $other_asset : $Asset       )  );
    
            _visitNode( %args,
                        Asset => $other_asset,
                        Depth => ($args{Depth}-1) );
        }
    }

}

sub _addEdge {

    my %args = (
        Graph => undef,
         Base => undef,
       Target => undef,
        Label => 'label me',
                 @_ );

    my %color = (
        DependsOn => 'red',
        RunsOn    => 'blue',
        LocatedAt => 'green',
        RefersTo  => 'black',
    );

    die "No graph object" unless ref($args{Graph});
    die "No asset object" unless ref($args{Base}) eq 'RTx::AssetTracker::Asset';
    die "No asset object" unless ref($args{Target}) eq 'RTx::AssetTracker::Asset';

    return if $AddedEdges{$args{Base}->id}{$args{Target}->id};
    return if $AddedEdges{$args{Target}->id}{$args{Base}->id};

    _addNode( Graph => $args{Graph}, Asset => $args{Base}   );
    _addNode( Graph => $args{Graph}, Asset => $args{Target} );
    $args{Graph}->add_edge( clean_name($args{Base}->Name) => clean_name($args{Target}->Name),
                            label => $args{Label}, color => $color{$args{Label}},
                           );

    $AddedEdges{$args{Base}->id}{$args{Target}->id} = 1;
    $AddedEdges{$args{Target}->id}{$args{Base}->id} = 1;
}

sub _addNode {

    my %args = (
        Graph => undef,
        Asset => undef,
        Style => 'solid',
        Shape => 'ellipse',
        Label => '',
        Force => 0,
                 @_ );

    die "No graph object" unless ref($args{Graph});
    die "No asset object" unless ref($args{Asset}) eq 'RTx::AssetTracker::Asset';

    return if ( $AddedNodes{$args{Asset}->id} && ! $args{Force} );

    my %rank = (
        "SFT Accounts"          => 8,
        "Custom Applications"   => 7,
        "Packaged Applications" => 7,
        "Mainframe"             => 6,
        "Misc"                  => 6,
        "Non-VW Storage"        => 6,
        "Non-VW Unix"           => 6,
        "Non-VW Windows"        => 6,
        "Unix"                  => 6,
        "Windows"               => 6,
        "Unix Components"       => 5,
        "Databases"             => 4,
        "Telecom"               => 3,
        "Storage"               => 3,
        "Printers"              => 3,
        "Network"               => 2,
        "Locations"             => 1,
    );

    my $uri = RT::URI->new( $RT::SystemUser );
    $uri->FromURI($args{Asset}->URI);
    my $url = $uri->Resolver->HREF();

    $args{Graph}->add_node( clean_name($args{Asset}->Name),
                            label => $args{Label} ? $args{Label} : clean_name($args{Asset}->Name),
                            rank  => $rank{$args{Asset}->TypeObj->Name},
                            shape => $args{Shape},
                            style => $args{Style},
                            URL   => $url );

    $AddedNodes{$args{Asset}->id} = $args{Asset}->Name;

}

sub other_asset {

    my %args = @_;
    my $uri = $args{URI};
    $uri or return;

    my $other_asset_uri = RT::URI->new($RT::SystemUser);
    $other_asset_uri->FromURI( $uri );
    my $other_object = undef;

    if ( $other_asset_uri->Resolver->Scheme eq 'at' ) {

        my $object = $other_asset_uri->Resolver->Object;
        if ( UNIVERSAL::isa( $object, 'RTx::AssetTracker::Asset' ) && $object->id ) {
            $other_object = $object;
        }
    }

    return $other_object;
}

sub clean_name {

    my $name = shift;
    $name =~ s/\W/ /g;
    return $name;

}

sub invert {

    my $type = shift;

    my $map = RTx::AssetTracker::Asset::LINKDIRMAP();
    $map->{LocatedAt}{Target} = 'AtThisLocation';
    return $map->{$type}{Target};

}

1;
